﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using COFE.Core.Logger;

namespace COFE.Core.Actions
{
    public class WorkScheduler : IWorkScheduler
    {

        #region Constructor

        public WorkScheduler(bool autoStart = true)
        {
            EventDispatcher = new EventDispatcher<WorkEventArgs>();
            _autoStart = autoStart;
        }

        #endregion

        #region Methods        
        private void resetAntStatus()
        {
            lock (_antList)
            {
                foreach (var ant in _antList)
                {
                    if (ant.Status == WorkAntStatus.Success || ant.Status == WorkAntStatus.Failed)
                    {
                        removeScheduled(ant.CurrentSchedule.Id, false);

                        ISchedule prevSchedule = ant.CurrentSchedule;
                        WorkAntStatus prevStatus = ant.Status;

                        ant.Reset(); //Reset ant.

                        if (!prevSchedule.IsFinished) //Need requeue.
                        {
                            if (prevSchedule.CheckCanRetry()) //Update retrycount if error.
                            {
                                EventDispatcher.DispatchEvent(this, new WorkEventArgs(prevSchedule.Work, EventType.Retry));
                                //Requeue.
                                addScheduled(prevSchedule, false);
                            }
                            else
                            {
                                //Out of retry, or IntervalSchedule aborted.
                            }
                        }


                    }

                }
            }

        }

        private void checkTotalAnt()
        {
            lock (_antList)
            {
                while (_antList.Count < TotalThread)
                    _antList.Add(new WorkSchedulerAnt(this, _antList.Count));
                while (_antList.Count > TotalThread)
                {
                    _antList.FirstOrDefault((ant) => ant.Status == WorkAntStatus.Initalized);
                    IWorkSchedulerAnt lastAnt = _antList.Last();

                    lastAnt.Abort();
                    _antList.Remove(lastAnt);
                }
            }
        }

        private void assignWork()
        {
            lock (_antList)
            {
                lock (_scheduledWork)
                    _scheduledWork.SortWork();

                foreach (var ant in _antList)
                    if (ant.Status == WorkAntStatus.Initalized)
                    {
                        var nextWork = popScheduledWork();
                        if (nextWork != null)
                            ant.AssignWork(nextWork);
                    }
            }
        }

        private void checkAborting()
        {
            if (_aborting && !_aborted)
            {
                lock (_antList)
                    if (!_antList.Any((ant) => ant.Status != WorkAntStatus.Initalized))
                        _aborted = true;

                if (_aborted)
                {
                    _totalThread = 0;
                    checkTotalAnt();
                }
            }
        }

        private static void schedulerProc(object input)
        {
            WorkScheduler scheduler = input as WorkScheduler;

            while (!scheduler._aborted)
            {
                scheduler._pauseTrigger.WaitOne();

                scheduler.resetAntStatus();
                scheduler.checkTotalAnt();
                scheduler.assignWork();
                scheduler.checkAborting();

                Thread.Sleep(1000);
            }
        }


        private ISchedule popScheduledWork()
        {
            lock (_processingWork)
                lock (_scheduledWork)
                    if (_scheduledWork.Count > 0)
                    {
                        var retVal = _scheduledWork[0];
                        if (retVal.Priority >= WorkPriority.Lowest)
                        {
                            _scheduledWork.RemoveAt(0);
                            _processingWork.Add(retVal);
                            return retVal;
                        }
                    }
            return null;
        }

        private bool removeScheduled(Guid scheduledWorkId, bool dispatchEvent)
        {
            var swi = GetScheduledWork(scheduledWorkId);
            if (swi == null)
                return false;
            lock (_scheduledWork)
                if (_scheduledWork.Contains(swi))
                    _scheduledWork.Remove(swi);
            lock (_processingWork)
                if (_processingWork.Contains(swi))
                    _processingWork.Remove(swi);

            if (dispatchEvent)
                EventDispatcher.DispatchEvent(this, new WorkEventArgs(swi.Work, EventType.Dequeued));
            return true;
        }

        private void addScheduled(ISchedule swi, bool dispatchEvent)
        {
            swi.ScheduledTimeUtc = DateTime.UtcNow;

            lock (_scheduledWork)                
                _scheduledWork.Add(swi);

            if (dispatchEvent)
                EventDispatcher.DispatchEvent(this, new WorkEventArgs(swi.Work, EventType.Queued));
            if (_aborted && _autoStart && !_aborting)
                Start();
        }

        protected virtual Guid enqueue(ISchedule schedule)
        {
            if (_aborting)
                throw new InvalidOperationException("Aborting, cannot enqueue.");
            
            lock (_processingWork)
                lock (_scheduledWork)
                {
                    ISchedule alreadyScheduled = null;
                    alreadyScheduled = _processingWork.FirstOrDefault(
                        (s) => s.Name == schedule.Name);
                    if (alreadyScheduled != null) return alreadyScheduled.Id;

                    alreadyScheduled = _scheduledWork.FirstOrDefault(
                        (s) => s.Name == schedule.Name);
                    if (alreadyScheduled != null) return alreadyScheduled.Id;
                }
                    


            addScheduled(schedule, true);
            return schedule.Id;
        }

        public virtual Guid Enqueue(WorkPriority priority, IWork work)
        {
            return enqueue(new OneSchedule(priority, work));
        }

        public virtual Guid Enqueue(WorkPriority priority, TimeSpan interval, IWork work)
        {
            return enqueue(new IntervalSchedule(priority, interval, work));
        }

        public virtual Guid Enqueue(WorkPriority priority, DateTime utcTime, IWork work)
        {
            return enqueue(new TimerSchedule(priority, utcTime, work));
        }

        public bool Dequeue(Guid scheduledWorkId)
        {
            return removeScheduled(scheduledWorkId, true);
        }

        public ISchedule GetScheduledWork(Guid scheduledWorkId)
        {
            ISchedule retVal = null;

            lock (_scheduledWork)
            {
                retVal = _scheduledWork.FirstOrDefault((wk) => wk.Id == scheduledWorkId);
                if (retVal != null)
                    return retVal;
            }
            lock (_processingWork)
            {
                retVal = _processingWork.FirstOrDefault((wk) => wk.Id == scheduledWorkId);
                if (retVal != null)
                    return retVal;
            }
            return null;
        }


        public void Start()
        {
            if (_aborting)
                throw new NotSupportedException();

            if (_aborted)
            {
                _aborted = false;
                _schedulerThread = new Thread(new ParameterizedThreadStart(schedulerProc));
                _schedulerThread.Name = "Scheduler";
                _schedulerThread.Start(this);
            }
        }

        public void Abort()
        {
            _aborting = true;
            var schedulerThread = _schedulerThread;
            if (schedulerThread != null)
                schedulerThread.Join(TimeSpan.FromMinutes(1));
        }

        public void Pause()
        {
            _paused = true;
            _pauseTrigger.Reset();
        }

        public void Resume()
        {
            _paused = false;
            _pauseTrigger.Set();
        }


        public IEnumerable<ISchedule> GetEnqueuedSchedules(Func<ISchedule, bool> filter = null)
        {
            if (filter == null)
                filter = (s) => true;
            return from s in _scheduledWork where filter(s) select s;
        }

        public IEnumerable<ISchedule> GetProcessingSchedules(Func<ISchedule, bool> filter = null)
        {
            if (filter == null)
                filter = (s) => true;
            return from s in _processingWork where filter(s) select s;
        }

        public override string ToString()
        {
            return "Scheduler";
            //lock(_antList)
            //    lock (_scheduledWork)
            //        lock (_processingWork)
            //return String.Format("S(A{0},Q{1},P{2})", _antList.Count(), _scheduledWork.Count(), _processingWork.Count());
        }

        #endregion

        #region Data

        Thread _schedulerThread;

        List<IWorkSchedulerAnt> _antList = new List<IWorkSchedulerAnt>();
        List<ISchedule> _scheduledWork = new List<ISchedule>();
        List<ISchedule> _processingWork = new List<ISchedule>();
        bool _paused = false, _aborting = false, _aborted = true, _autoStart = false;
        ManualResetEvent _pauseTrigger = new ManualResetEvent(true);
        int _totalThread = 3;
        int _retryCount = 3;

        #endregion

        #region Public Properties
        
        public IWorkSchedulerAnt[] WorkAnts
        {
            get { lock (_antList) return _antList.ToArray(); }
        }

        public int TotalThread
        {
            get { return _totalThread; }
            set { if (value <= 0) throw new ArgumentException(); _totalThread = value; }
        }
        public int RetryCount { get { return _retryCount; } set { _retryCount = value; } }
        public ISchedule this[Guid scheduledWorkId] { get { return GetScheduledWork(scheduledWorkId); } }

        public IEventDispatcher<WorkEventArgs> EventDispatcher { get; private set; }

        #endregion


    }
}
